package main

import (
	"github.com/ngaut/log"
	"github.com/shopspring/decimal"
	"github.com/garyburd/redigo/redis"
	"global/constants"
	"sync"
	"global/structs"
	"time"
	"fmt"
	"server/order/model"
	"global/function"
	"encoding/json"
	"strings"
)

type Market struct {
	sync.Mutex
	wg 			sync.WaitGroup
	ctx 		*Context
}

func NewMarket(ctx *Context) *Market {
	return &Market{
		ctx: 		ctx,
	}
}

const (
	BTC2USD = "btc_usd"
	USD2CNY = "usd_cny"
	ETH2USD = "eth_usd"
	DKKT2USD = "dkkt_usd"
)

var CoinToCoinPrice map[string]string

func init() {
	CoinToCoinPrice = make(map[string]string)
	CoinToCoinPrice[BTC2USD] = ""
	CoinToCoinPrice[USD2CNY] = ""
	CoinToCoinPrice[ETH2USD] = ""
	CoinToCoinPrice[DKKT2USD] = ""
}

var Decimal6Btc = map[string]bool{}
var Decimal6Eth = map[string]bool{
	"doge_eth": true,
}

func (self *Market) run() {

	if len(self.ctx.Data.CoinPair) == 0 {
		log.Error("get coin pairs error")
		return
	}

	conn := self.ctx.GetOteRedis().Get()
	if conn == nil {
		log.Error("get ote redis pool error")
		return
	}
	defer conn.Close()

	priceB2U := getPrice(conn, BTC2USD)
	priceU2C := getPrice(conn, USD2CNY)
	priceE2U := getPrice(conn, ETH2USD)
	priceD2U := getPrice(conn, DKKT2USD)

	log.Infof("get price, b2u: %v, u2c: %v, e2u: %v", priceB2U, priceU2C, priceE2U)

	var result = make(map[string]map[string]string)

	log.Infof("coin pair lengths: %d", len(self.ctx.Data.CoinPair))
	self.wg.Add(len(self.ctx.Data.CoinPair))

	for _, pair := range self.ctx.Data.CoinPair {
		go func(coinPair structs.CoinPair) {
			coinPairConn := self.ctx.GetOteRedis().Get()
			defer func() {
				coinPairConn.Close()
				self.wg.Done()
			}()

			coinInfo, last := getCoinInfo(coinPairConn, coinPair)
			if coinPair.CoinTo == "btc" {
				usd := last.Mul(priceB2U)
				cny := usd.Mul(priceU2C)
				if _, ok := Decimal6Btc[coinPair.Name]; ok {
					coinInfo["usd"] = usd.StringFixedBank(6)
					coinInfo["cny"] = cny.StringFixedBank(6)
				} else {
					coinInfo["usd"] = usd.StringFixedBank(8)
					coinInfo["cny"] = cny.StringFixedBank(8)
				}
			} else if coinPair.CoinTo == "eth" {
				usd := last.Mul(priceE2U)
				cny := usd.Mul(priceU2C)

				if _, ok := Decimal6Eth[coinPair.Name]; ok {
					coinInfo["usd"] = usd.StringFixedBank(6)
					coinInfo["cny"] = cny.StringFixedBank(6)
				} else {
					coinInfo["usd"] = usd.StringFixedBank(8)
					coinInfo["cny"] = cny.StringFixedBank(8)
				}
			} else if coinPair.CoinTo == "usdt" || coinPair.CoinTo == "usd" {
				u2c := last.Mul(priceU2C)
				coinInfo["usd"] = last.StringFixedBank(8)
				coinInfo["cny"] = u2c.StringFixedBank(8)
			} else if coinPair.CoinTo == "dkkt" {
				usd := last.Mul(priceD2U)
				cny := usd.Mul(priceU2C)
				coinInfo["usd"] = usd.StringFixedBank(8)
				coinInfo["cny"] = cny.StringFixedBank(8)
			}

			self.Lock()
			result[coinPair.Name] = coinInfo
			self.Unlock()

		}(pair)
	}

	self.wg.Wait()

	log.Debugf("all goroutines has be done, now format it")

	// 组合成前端需要的分类格式
	formatResult := make(map[string][]map[string]string)
	for key, value := range result {
		keys := strings.Split(key, "_")
		if len(keys) < 2 {
			continue
		}

		coinTo := keys[1]
		if _, ok := formatResult[coinTo]; !ok {
			formatResult[coinTo] = make([]map[string]string, 0)
		}

		formatResult[coinTo] = append(formatResult[coinTo], value)
	}

	resultBytes, err := json.Marshal(formatResult)
	if err != nil {
		log.Errorf("json marshal info fresh error, msg: %s", err.Error())
		return
	}

	_, err = conn.Do("SET", constants.OrderInfoFresh, string(resultBytes))
	if err != nil {
		log.Errorf("cache save info fresh error, msg: %s", err.Error())
	}
}

func getCoinInfo(conn redis.Conn, pair structs.CoinPair) (map[string]string, decimal.Decimal) {
	arr := map[string]string{
		"name": 		pair.Name,
		"price_float": 	fmt.Sprintf("%d", pair.FloatPrice),
		"number_float": fmt.Sprintf("%d", pair.FloatNumber),
		"last":     	"0.00000000",
		"max":      	"0.00000000",
		"min":      	"0.00000000",
		"vol_24":   	"0.0000",
		"money_24": 	"0.00000000",
		"float_24": 	"0.00",
		"trend": 		"1",
		"cny": 			"0.00000000",
		"usd": 			"0.00000000",
	}

	analysis := getAnalysisResults(conn, pair.Name)

	var last, prev decimal.Decimal
	if analysis != nil {
		last, _ = decimal.NewFromString(analysis.LastPrice)
		prev, _ = decimal.NewFromString(analysis.PrePrice)

		if last.LessThan(prev) {
			arr["trend"] = "0"
		}

		arr["last"] = analysis.LastPrice
		arr["max"] = analysis.Max
		arr["min"] = analysis.Min
	}

	start := int(time.Now().Add(-1 * time.Hour * 24).Unix())

	numAndMoney := model.OrderCoinModel.Get24HoursNumAndMoney(pair.CoinFrom, pair.CoinTo, start)
	if numAndMoney != nil {
		if num, ok := numAndMoney["num"]; ok {
			vol, _ := decimal.NewFromString(string(num))
			arr["vol_24"] = vol.StringFixedBank(8)
		}
		if money, ok := numAndMoney["money"]; ok {
			m, _ := decimal.NewFromString(string(money))
			arr["money_24"] = m.StringFixedBank(8)
		}
	}

	// 开盘价
	upAndDown := model.OrderCoinModel.Get24UpAndDown(pair.CoinFrom, pair.CoinTo, start)
	percent100 := decimal.NewFromFloat(100)
	openPrice := decimal.NewFromFloat(0)
	if upAndDown != nil && upAndDown.Id > 0 {
		udPrice, _ := decimal.NewFromString(upAndDown.Price)
		openPrice = udPrice
		if !udPrice.Equal(decimal.Zero) {
			result := (last.Sub(udPrice)).Div(udPrice).Mul(percent100).StringFixedBank(2)
			arr["float_24"] = result
			log.Infof("first: %s, last: %s, percent: %s", udPrice.StringFixedBank(2), last.StringFixedBank(2), result)
		}
	} else {
		first := model.OrderCoinModel.GetFirstOrder(pair.CoinFrom, pair.CoinTo)
		if first != nil && first.Id > 0 {
			openPrice, _ = decimal.NewFromString(first.Price)
			fPrice, _ := decimal.NewFromString(first.Price)
			if !fPrice.Equal(decimal.Zero) {
				result := (last.Sub(fPrice)).Div(fPrice).Mul(percent100).StringFixedBank(2)
				arr["float_24"] = result
				log.Infof("first: %s, last: %s, percent: %s", fPrice.StringFixedBank(2), last.StringFixedBank(2), result)
			}
		}
	}

	calcTicker(conn, arr, pair.Name, openPrice)

	return arr, last
}

func calcTicker(conn redis.Conn, arr map[string]string, coinPair string, open decimal.Decimal) {

	firstBuyPrice, firstSalePrice := "0.00000000", "0.00000000"
	buyDepths, err := redis.Strings(conn.Do("LRANGE", function.GetDepthKey(coinPair, "buy", 8), 0, 0))
	if err != nil {
		log.Errorf("get buy depths error, msg: %s", err.Error())
	} else if len(buyDepths) > 0 {
		var depth structs.Depth
		err := json.Unmarshal([]byte(buyDepths[0]), &depth)
		if err != nil {
			log.Errorf("json unmarshal depth data error, msg: %s", err.Error())
		} else {
			firstBuyPrice = depth.Price
		}
	}

	saleDepths, err := redis.Strings(conn.Do("LRANGE", function.GetDepthKey(coinPair, "sale", 8), 0, 0))
	if err != nil {
		log.Errorf("get sale depth error, msg: %s", err.Error())
	} else if len(saleDepths) > 0 {
		var depth structs.Depth
		err := json.Unmarshal([]byte(saleDepths[0]), &depth)
		if err != nil {
			log.Errorf("json unmarshal sale depth error, %s", err.Error())
		} else {
			firstSalePrice = depth.Price
		}
	}

	ticker := structs.Ticker{
		Last: 		arr["last"],
		High: 		arr["max"],
		Low: 		arr["min"],
		Buy: 		firstBuyPrice,
		Sell: 		firstSalePrice,
		NumberVol: 	arr["vol_24"],
		MoneyVol: 	arr["money_24"],
		Float24h: 	arr["float_24"],
		Open: 		open.StringFixedBank(8),
	}

	marshaledTicker, err := json.Marshal(ticker)
	if err != nil {
		log.Errorf("json marshal ticker data error, msg: %s", err.Error())
		return
	}

	_, err = conn.Do("HSET", constants.OrderTickerInfo, coinPair, string(marshaledTicker))
	if err != nil {
		log.Errorf("hset ticker data error, msg: %s", err.Error())
	}
}

// 获取价格
func getPrice(conn redis.Conn, key string) decimal.Decimal {
	val, err := redis.String(conn.Do("HGET", constants.CommonPriceKey, key))
	if err != nil {
		log.Errorf("get price error, key: %s, err: %s", key, err.Error())
		return decimal.Zero
	}
	price, _ := decimal.NewFromString(val)
	return price
}